#ifndef INCLUDE_SORTER_H
#define INCLUDE_SORTER_H

#include <QtCore/QDir>
#include <QtCore/QStack>
#include <QtCore/QString>
#include <QtCore/QTextStream>

namespace tools
{

class Sorter
{
public:
    Sorter(const QString& projectPath) : m_projectPath(projectPath)
    {

    }

    void sort()
    {
        QDir project(m_projectPath);

        QStack<QDir> stack;
        stack.push(project);

        unsigned int processedCounter = 0;

        if (project.exists())
        {
            qWarning("\nStarted sorting...\n\n");

            while (stack.size() > 0)
            {
                QDir current = stack.pop();

                const QFileInfoList children = current.entryInfoList(
                    QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);

                QFileInfoList suitableForSorting;

                foreach (const QFileInfo& info, children)
                {
                    if (info.isDir())
                    {
                        QDir child(info.absoluteFilePath());

                        const QString dirName = child.dirName();

                        if (dirName != ".svn")
                        {
                            stack.push(child);
                        }
                    }
                    else if ((info.suffix() == "h" || info.suffix() == "cpp")
                        && !info.fileName().startsWith("moc_"))
                    {
                        suitableForSorting.append(info);
                    }
                }

                sort(suitableForSorting);
                processedCounter += suitableForSorting.size();
            }
        }
        else
        {
            qWarning("Project path you specified doesn't exist.");
        }

        qWarning("\nSorted %d files.", processedCounter);
    }

private:
    void sort(const QFileInfoList& files)
    {
        foreach (const QFileInfo& info, files)
        {
            QString beforeInclude;
            QString afterInclude;

            QString cppHeaderInclude;

            QStringList localIncludes;

            QStringList componentIncludes;

            QStringList qtIncludes;

            QFile file(info.absoluteFilePath());

            unsigned int emptyLinesCounter = 0;

            if (file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                QTextStream stream(&file);

                bool afterFirstInclude = false;

                qWarning("Sorting %s ...\n", info.fileName().toUtf8(
                    ).constData());

                while (!stream.atEnd())
                {
                    QString line = stream.readLine();

                    if (line.isEmpty())
                    {
                        ++emptyLinesCounter;
                    }
                    else
                    {
                        emptyLinesCounter = 0;
                    }

                    if (line.startsWith("#include"))
                    {
                        afterFirstInclude = true;

                        QRegExp regex("#include \"(.*)\"\\s*");

                        const QString hFile = QString("%1.h").arg(
                            info.baseName());

                        if (regex.exactMatch(line))
                        {
                            if (regex.cap(1) == hFile)
                            {
                                cppHeaderInclude = QString("%1\n").arg(line);
                            }
                            else
                            {
                                localIncludes.append(QString("%1\n").arg(
                                    line));
                            }
                        }
                        else
                        {
                            regex.setPattern("#include <.*>\\s*");

                            if (regex.exactMatch(line))
                            {
                                regex.setPattern("#include <Qt(.*)>\\s*");

                                if (regex.exactMatch(line))
                                {
                                    qtIncludes.append(QString("%1\n").arg(
                                        line));
                                }
                                else
                                {
                                    componentIncludes.append(
                                        QString("%1\n").arg(line));
                                }
                            }
                        }
                    }
                    else
                    {
                        if (emptyLinesCounter <= 1)
                        {
                            if (afterFirstInclude)
                            {
                                afterInclude += afterInclude.isEmpty()
                                    && line.isEmpty() ? QString("%1").arg(
                                        line) : QString("%1\n").arg(line);
                            }
                            else
                            {
                                beforeInclude += beforeInclude.isEmpty()
                                    && line.isEmpty() ? QString("%1").arg(
                                        line) : QString("%1\n").arg(line);
                            }
                        }
                    }
                }

                localIncludes.sort();
                componentIncludes.sort();
                qtIncludes.sort();

                file.close();
                if (file.open(QIODevice::WriteOnly | QIODevice::Truncate |
                    QIODevice::Text))
                {
                    QTextStream stream(&file);

                    stream << beforeInclude;
                    stream << cppHeaderInclude;
                    stream << (!cppHeaderInclude.isEmpty() ? "\n" : "");
                    stream << localIncludes.join("");
                    stream << (localIncludes.size() > 0 ? "\n" : "");
                    stream << componentIncludes.join("");
                    stream << (componentIncludes.size() > 0 ? "\n" : "");
                    stream << qtIncludes.join("");
                    stream << (qtIncludes.size() > 0 ? "\n" : "");
                    stream << afterInclude;
                }
            }

        }
    }

private:
    QString m_projectPath;
};

} // namespace tools

#endif
